Програми
Scala пишуться з використанням набору символів Unicode Basic Multilingual
Plane (BMP); додаткові символи Unicode наразі не підтримуються.
Ця глава визначає два режими лексичного синтаксису Scala, режим Scala та XML режим. кщо не
вказане інше, наступні дескриптори токенів Scala посилаються на
режим Scala, та літеральні символи ‘c’ посилаються на
фрагмент ASCII \u0000 – \u007F.
В режимі Scala, виключення Unicode замінюються на відповідні символи Unicode з наданим шістнацятиричним кодом.
UnicodeEscape ::= ‘\’ ‘u’ {‘u’} hexDigit hexDigit hexDigit hexDigit
hexDigit ::= ‘0’ | … | ‘9’ | ‘A’ | … | ‘F’ | ‘a’ | … | ‘f’
Щоб побудувати токени, символи визначаються за наступними класами (загальна категорія Unicode надається в дужках):
\u0020
| \u0009 | \u000D | \u000A.Ll),
великі літери (Lu),
заголовні літери (Lt),
інші літери (Lo),
літери-цифри (Nl)
та два символи, \u0024
‘$’ та
\u005F
‘_’, що обоє вважаються за великі літери.‘0’
| … | ‘9’.‘(’
| ‘)’ | ‘[’ | ‘]’ | ‘{’ | ‘}’.‘`’
| ‘'’ | ‘"’ | ‘.’ | ‘;’ | ‘,’.\u0020 - \u007F, що не входять до наборів вище,
математичні символи (Sm)
та інші символи (So).op ::= opchar {opchar}
varid ::= lower idrest
plainid ::= upper idrest
| varid
| op
id ::= plainid
| ‘`’ stringLiteral ‘`’
idrest ::= {letter | digit} [‘_’ op]
Є
три способи сформувати ідентифікатор. Перше, ідентифікатор може починатись
з довільної послідовності літер та цифр. За цім може слідувати символи
підкреслення ‘_‘ та інші рядки, що складаються з літер та
цифр, або символи операторів. Друге, ідентифікатор може
починатись з символа оператора, за яким може слідувати довільна
послідовність символів операторів. Ці дві форми називаються простими ідентифікаторами. Нарешті,
ідентифікатор може також формуватись з довільного рядка між зворотніми
лапками (різні системи можуть накладати деякі обмеження до того, які рядки
легальні в якості ідентифікаторів). Самий ідентифікатор складається з усіх
символів, за винятком зворотніх лапок.
Як звичайно, застосовується найбільше співпадіння. Наприклад, рядок
big_bob++=`def`
розбивається
на три ідентифікатори, big_bob, ++=,
та def.
Правила для порівняння з шаблоном додатково розрізняє між ідентифікаторами
змінних, які починаються з малих літер, та ідентифікаторами
констант, що ні.
Символ ‘$’ зарезервовано для синтезованих компілятором ідентифікаторів. Користувацькі програми не повинні визначати ідентифікатори, що мають символи ‘$’.
Наступні
імена є зарезарвованими словами, замість бути членами id синтаксичного класу
або лексічними ідентифікаторами.
abstract case catch class def
do else extends false final
finally for forSome if implicit
import lazy macro match new
null object override package private
protected return sealed super this
throw trait try true type
val var while with yield
_ : = => <- <: <% >: # @
Оператори
Unicode \u21D2 ‘\u2190 ‘=> та
<-,
також зарезервовані, .
Ось приклади ідентифікаторів:
x Object maxIndex p2p empty_? + `yield` αρετη _y dot_product_* __system _MAX_LEN_
Коли треба отримати доступ до Java ідентифікаторів, що є зарезервованими словами в Scala, використовуйте рядки в зворотніх лапках. Наприклад, твердження
Thread.yield()є нелегальним, оскількиyieldє зарезервованим словом в Scala. Однак наступне є обхідним шляхом:Thread.`yield`()
semi ::= ‘;’ | nl {nl}
Scala є рядок-орієнтованою мовою, де твердження можуть бути завершені крапкою з комою, або новим рядком. Новий рядок в джерельному тексті Scala трактується як специальний токен “nl”, якщо задовільняються три критерії:
Токени, що можуть завершити твердження, такі: літерали, ідентифікатори та роздільники та зарезервовані слова:
this null true false return type
_ ) ] }
Токени, що можуть починати твердження, є всі токени Scala, за винятком наступних роздільників та зарезервованих слів:
catch else extends finally forSome match
with yield , . ; : = => <- <: <%
>: # [ ) ] }
Токен case може починати твердження, тільки
якщо слідує за токенами class або
object.
Нові рядки дозволені у:
{
},
за виключенням вкладених регіонів, де нові рядки відключені.Нові рядки відключені у:
( та
),
за виключенням вкладених регіонів, де нові рядки дозволені, та [ та
],
за виключенням вкладених регіонів, де нові рядки дозволені. case та його співпадаючим токеном =>,
за винятком вкладених регіонів, що нові рядки дозволені. Занотуйте,
що фігурні дужки {...} заекрановані в XML, та рядкові
літерали не є токенами, і, таким чином, не замикають регіон, де включені
нові рядки.
Звичайно,
вставляється тільки один токен nl
між двома
послідовними токенами не-новими-рядками, що є на різних рядках, навіть
якщо між двома токенами більше одного рядка. Однак, якщо два токени
розділені щонайменьше одним повністю пустим рядком (тобто рядком, що не
містить друкованих символів), тоді вставляються два токени nl.
Граматика
Scala (повністю надана тут)
містить конструкції, де допустимі опціональні nl, але но додаткові крапки з комою.
Це має ефект, що новий рядок на одній з ціх позицій не завершують вираз
або твердження. Ці позиції можуть бути підсумовані наступним чином:
Декілька токенів нових рядків допустимі в наступних місцях (зауважте, що крапка з комою замість нового рядка може бути нелегальним в кожному з ціх випадків):
type в
визначенні
або декларації типу.Поодинокий токен нового рядка прийнятний
Токени нового рядка між двома рядками не трактуються як роздільники тверджень.
if (x > 0) x = x - 1 while (x > 0) x = x / 2 for (x <- 1 to 10) println(x) type IntList = List[Int]
new Iterator[Int] { private var x = 0 def hasNext = true def next = { x += 1; x } }З додатковим символом нового рядка той же код інтерпретується як створення об'єкта, за яким слідує локальний блок:
new Iterator[Int] { private var x = 0 def hasNext = true def next = { x += 1; x } }
x < 0 || x > 10З додатковим символом нового рядка той же код інтерпретуюється як два вираза:
x < 0 || x > 10
def func(x: Int) (y: Int) = x + yЗ додатковим символом нового рядка той же код інтерпретується як визначення абстрактної функції, та синтаксично нелегального твердження:
def func(x: Int) (y: Int) = x + y
@serializable protected class Data { ... }З додатковим символом нового рядка той же код інтерпретується як атрибут та окреме твердження (що синтаксично нелегально).
@serializable protected class Data { ... }
Є літерали для цілих чисел, чисел з плаваючою крапкою, символів, логічних, символічних та рядків. Синтаксис ціх літералів в кожному випадку такий же, як в Java.
Literal ::= [‘-’] integerLiteral
| [‘-’] floatingPointLiteral
| booleanLiteral
| characterLiteral
| stringLiteral
| symbolLiteral
| ‘null’
integerLiteral ::= (decimalNumeral | hexNumeral)
[‘L’ | ‘l’]
decimalNumeral ::= ‘0’ | nonZeroDigit {digit}
hexNumeral ::= ‘0’ (‘x’ | ‘X’) hexDigit {hexDigit}
digit ::= ‘0’ | nonZeroDigit
nonZeroDigit ::= ‘1’ | … | ‘9’
Цілі
літерали звичайно мають тип Int,
або тип Long, якщо за ним слідує суфікс
L або
l.
Значення типу Int є всі цілі числа в межах
Long є всі цілів межах
Однак,
якщо очікуваний тип pt літерала у виразі є Byte, Short,
або Char, та ціле число в межах, що визначені
типом, тоді число конвертується до типу pt і тип літерала є
pt. Числові диапазони, надані цім типам, наступні:
Byte |
|
Short |
|
Char |
0 21 0xFFFFFFFF -42L
floatingPointLiteral ::= digit {digit} ‘.’ digit {digit} [exponentPart] [floatType]
| ‘.’ digit {digit} [exponentPart] [floatType]
| digit {digit} exponentPart [floatType]
| digit {digit} [exponentPart] floatType
exponentPart ::= (‘E’ | ‘e’) [‘+’ | ‘-’] digit {digit}
floatType ::= ‘F’ | ‘f’ | ‘D’ | ‘d’
Літерали
з плаваючою крапкою є типу Float, коли за типом з плаваючою
крапкою слідує суфікс F або
f,
та типу Double
в іншому порядку. Тип Float складається з усіх 32-бітних значень
одинарної точності з плаваючою крапкою, що задовільняють IEEE
754, тоді як Double складається з 64-бітних значень
подвійної точності.
Якщо за літералом з плаваючою крапкою в програмі слідує літера, має бути щонайменьше один символ проміжку між двома токенами.
0.0 1e30f 3.14159f 1.0e-100 .1
Фраза
1.toStringрозкладається на три різні токени: ціле-літерал1, крапка., та ідентифікаторtoString.
1.не є дійсним літералом з плаваючою крапкою, оскільки відсутня обов'язкова цифра після крапки.
booleanLiteral ::= ‘true’ | ‘false’
Логічні
літерали true та
false є членами типу
Boolean.
characterLiteral ::= ‘'’ (printableChar | charEscapeSeq) ‘'’
Символьні літерали є поодиноким символом у лапках. Символ або друкований символ юнікод або описаний як екранована послідовність.
'a' '\u0041' '\n' '\t'
Зауважте,
що '\u000A' не є дійсним
символьним літералом, оскільки перетворення Unicode відбувається перед
розбором літерала, та символ Unicode \u000A (переведення рядка) не є
друкованим символом. Замість цього можна використати екрановану
послідовність '\n' або восьмирічну
послідовність '\12'(дивіться тут).
stringLiteral ::= ‘"’ {stringElement} ‘"’
stringElement ::= printableCharNoDoubleQuote | charEscapeSeq
Літеральний
рядок є послідовністю символів в подвійних лапках. Символи є або
друкованими символами юнікоду, або визначені як
екрановані
послідовності. Якщо літеральний рядок містить символ подвійних
лапок, він може бути екранований як "\"".
Значення рядкового літералу є примірником класу
String.
"Hello,\nWorld!" "This string contains a \" character."
stringLiteral ::= ‘"""’ multiLineChars ‘"""’
multiLineChars ::= {[‘"’] [‘"’] charNoDoubleQuote} {‘"’}
Багато-рядковий
рядковий літерал є послідовністю символів, заключених в потрійні лапки """
... """. Послідовність символів довільна, за тим виключенням, що
вона може містити три або більше послідовних символів подвійних лапок
тільки в самому кінці. Символи не обов'язково мають бути друкованими.
Екранування Unicode робить як у всіх інших місцях, але жодне з екранованих
послідовностей тут
не інтерпретуються.
"""the present string spans three lines."""Це буде продукувати рядок:
the present string spans three lines.Бібліотека Scala містить допоміжний метод
stripMargin, що може бути використаний для видалення проміжків напочатку багато-рядкових літералів. Вираз"""the present string |spans three |lines.""".stripMarginобчислюється до
the present string spans three lines.Метод
stripMarginвизначений в класі scala.collection.immutable.StringLike. Оскільки існує передвизначене неявне перетворення зіStringнаStringLike, метод стосується до всіх рядків.
Наступні екрановані послідовності різпознаються в символьних та рядкових літералах.
| послідовність | юнікод | ім'я | символ |
|---|---|---|---|
‘\‘
‘b‘ |
\u0008 |
backspace | BS |
‘\‘
‘t‘ |
\u0009 |
horizontal tab | HT |
‘\‘
‘n‘ |
\u000a |
linefeed | LF |
‘\‘
‘f‘ |
\u000c |
form feed | FF |
‘\‘
‘r‘ |
\u000d |
carriage return | CR |
‘\‘
‘"‘ |
\u0022 |
double quote | " |
‘\‘
‘'‘ |
\u0027 |
single quote | ' |
‘\‘
‘\‘ |
\u005c |
backslash | \ |
Символ
з Unicode між 0 та 255 може бути також представлено як екрановане
восьмиричне, точто як коса '\', за якою слідує послідовність до трьох
восьмиричних символів.
Буде помилкою часу компіляції, якщо за символом косої лінії в символьному або рядковому літералі не починається дійсна екранована послідовність.
symbolLiteral ::= ‘'’ plainid
Символічний
літерал 'x є скороченням для виразу scala.Symbol("x"). Symbol є кейс класом,
що визначений наступним чином.
package scala
final case class Symbol private (name: String) {
override def toString: String = "'" + name
}
Метод apply компанійського об'єкту Symbol
кешує слабкі посилання на Symbol,
таким чином забезпечуючи, що ідентичні символічні літерали еквівалентні з
точки зору еквівалентності посилань.
Токени можуть бути розділені символами-проміжками та/або коментарями. Коментарі слідують в двох формах:
Одно-рядкові
коментарі є послідовністю символів, що починаються з // , та продовжуються до кінця рядка.
Багато-рядковий
коментар є послідовністю символів між /* та
*/.
Багато-рядкові коментарі можуть бути вкладені, але мають бути вірно
вкладеними. Таким чином, коментар як /*
/* */ буде відхілений, як
такий, що незачинений.
Щоб дозволити літеральні включення з XML фрагментів, лексічний аналіз перемикається з режиму Scala до режиму XML, коли натрапляє на відкливаючу кутову дужку, ‘<’, за наступних умов: ‘<’ має бути попереджений проміжком, відкритими дужками або фігурними дужками, та безпосередньо за ним має іти символ ім'я XML.
( whitespace | ‘(’ | ‘{’ ) ‘<’ (XNameStart | ‘!’ | ‘?’)
XNameStart ::= ‘_’ | BaseChar | Ideographic // as in W3C XML, but without ‘:’
Сканер перемикається з режиму XML до режиму Scala за наступної умови
Зауважте, що токени Scala конструюються в XML режимі, та що коментарі інтерпретуються як текст.
Наступнє визначення значення використовує XML літерал з двома вбудованими виразами Scala:
val b =The Scala Language Specification {scalaBook.version} {scalaBook.authors.mkList("", ", ", "")}
Імена в Scala ідентифікують типи, значення, методи та класи, що загалом називаються сутностями. Імена вводяться локальними визначеннями та деклараціями, наслідуванням, положеннями імпорту, або положеннями пакунків, що колективно називаються прив'язками.
Прив'язки різних типів мають визначене для них старшинство:
Існує два різних простору імен, одне для типів та один для термів. Те ж ім'я може позначати тип та терм, в залежності від контексту, де використовується ім'я.
Прив'язка має сферу , в якій визначена єдиним іменем сутність може бути доступною за допомогою простого імені. Сфери вкладені. Прив'язка в деякій внутрішній сфері затінює прив'язки меньшого приорітету в тій же сфері, так само, як прив'язки такого ж, або нижчого приорітету, у зовнішніх сферах.
Посилання
до некваліфікованого ідентифікатора (типу- або терміну-)
Буде
помилкою, якщо така прив'язка не існує. Якщо
Посилання
на кваліфікований ідентифікатор (типу- або терму-),
Нехай
є два наступні визначення об'єктів, з іменами X в пакунках
P та
Q.
package P {
object X { val x = 1; val y = 2 }
}
package Q {
object X { val x = true; val y = "" }
}
Наступна програма ілюструє різні типи прив'язок, та відносини старшості між ними.
package P { // `X' прив'язане через package
import Console._ // `println' прив'язане через узагальнений import
object A {
println("L4: "+X) // `X' посилається на `P.X'
object B {
import Q._ // `X' прив'язаний через узагальнений import
println("L7: "+X) // `X' тут посилається на `Q.X'
import X._ // `x' та `y' прив'язані через узагальнений import
println("L8: "+x) // `x' тут посилається на `Q.X.x'
object C {
val x = 3 // `x' прив'язаний локальним визначенням
println("L12: "+x) // `x' тут посилається на константу `3'
{ import Q.X._ // `x' та `y' прив'язані через узагальнений import
// println("L14: "+x) // посилання на `x' тут неоднозначне
import X.y // `y' прив'язане явним import
println("L16: "+y) // `y' тут посилається на `Q.X.y'
{ val x = "abc" // `x' прив'язане локальним визначенням
import P.X._ // `x' та `y' прив'язані узагальненим import
// println("L19: "+y) // посилання на `y' тут неоднозначне
println("L20: "+x) // `x' тут посилаєтся на "abc"
}}}}}}
Type ::= FunctionArgTypes ‘=>’ Type
| InfixType [ExistentialClause]
FunctionArgTypes ::= InfixType
| ‘(’ [ ParamType {‘,’ ParamType } ] ‘)’
ExistentialClause ::= ‘forSome’ ‘{’ ExistentialDcl
{semi ExistentialDcl} ‘}’
ExistentialDcl ::= ‘type’ TypeDcl
| ‘val’ ValDcl
InfixType ::= CompoundType {id [nl] CompoundType}
CompoundType ::= AnnotType {‘with’ AnnotType} [Refinement]
| Refinement
AnnotType ::= SimpleType {Annotation}
SimpleType ::= SimpleType TypeArgs
| SimpleType ‘#’ id
| StableId
| Path ‘.’ ‘type’
| ‘(’ Types ‘)’
TypeArgs ::= ‘[’ Types ‘]’
Types ::= Type {‘,’ Type}
Ми робимо різницю між першого порядку та конструкторами типів, що сприймають параметри тиів, та віддають типи. Підмножина типів першого порядку називаються типами значень, що представляють набори (першокласних) значень. Типи значень є або конкретними або абстрактними.
Кожне конкретне значення може бути представлене тип класу, тобто покажчиком типу, що посилаєтся на клас або трейт 1, або як складний тип, що представляє перетин типів, можливо з уточнення, що ще обмежує типи своїх членів.
Абстрактні типи даних введені параметрами типів та абстрактними прив'язками типів. Дужки в типах можуть використовуватись для групування.
Типи не-значень захоплюють властивості ідентифікаторів, що не є значеннями. Наприклад, конструктор типу не задає напряму тип значень. Однак, коли конструктор типу застосовується з коректними аргументами типу, він надає тип першого порядку, що може бути типом значення.
Типи
не-значень виражені неявно в Scala. Тобто, метод типу описується,
записуючи сигнатуру метода, що насправді не є реальним типу, хоча воно
породжує відповідний тип методу.
Конструктори типу є іншим прикладом, так що можна записати Swap[m[_,
_], a,b] = m[b, a], але немає синтаксиса для запису відповідного
анонімного типу функції напряму.
Path ::= StableId
| [id ‘.’] this
StableId ::= id
| Path ‘.’ id
| [id ‘.’] ‘super’ [ClassQualifier] ‘.’ id
ClassQualifier ::= ‘[’ id ‘]’
Шляхи не є типами самі по собі, але вони можуть бути частиною імен типів, та в цій функції формують центральну роль в системі типів Scala.
Шляхом є одне з наступних.
this,
де this береться як скорочення для this де supersupersuper береться як скорочення до super, де Стабільний ідентифікатор є шляхом, що завершується на ідентифікатор.
Кожне значення в Scala має тип, що є однією з наступних форм.
SimpleType ::= Path ‘.’ type
Тип-синглтон
є форма type,
де scala.AnyRef.
Тип визначає набір значень, що складаються з null та значення, визначеного
Стабільний тип є або тип-синглтон, або тип, що
декларований як субтип трейту scala.Singleton.
SimpleType ::= SimpleType ‘#’ id
Проекція
типу
SimpleType ::= StableId
Визначник типу посилаєтся на іменоване значення типу. Він може бути простим або кваліфікованим. Всі таки визначники типу є скороченнями до проекцій типу.
Зокрема,
некваліфіковане ім'я типу this.type#.type#
Кваліфікований
визначник типу має форму p.t , де p є
шляхом та t є ім'ям типу. Такий визначник
типу еквівалентний до проекції типу p.type#t.
Деякі
визначники типу та їх розширення перелічені нижче. Ми маємо на увазі
локальний параметр типу maintable з членом типу Node ,та стандартний клас scala.Int,
| Визначення | Розширення |
|---|---|
| t | ε.type#t |
| Int | scala.type#Int |
| scala.Int | scala.type#Int |
| data.maintable.Node | data.maintable.type#Node |
SimpleType ::= SimpleType TypeArgs
TypeArgs ::= ‘[’ Types ‘]’
Параметризований
тип
Скажімо,
параметри типу мають нижчі межі
Візьмемо часткові візначення типів:
class TreeMap[A <: Comparable[A], B] { … }
class List[A] { … }
class I extends Comparable[I] { … }
class F[M[_], X] { … }
class S[K <: String] { … }
class G[M[ Z <: I ], I] { … }
наступні параметризовані типи є гарно сформованими:
TreeMap[I, String]
List[I]
List[List[Boolean]]
F[List, Int]
G[S, String]
Беручи до уваги зазначені вище визначення, наступні типи є хворобливо-зформованими:
TreeMap[I] // недійсне: помилкове число параметрів
TreeMap[List[I], Int] // недійсне: параметр типу не в межах
F[Int, Boolean] // недійсне: Int не є конструктором типу
F[TreeMap, Int] // недійсне: TreeMap приймає два параметри,
// F очікує конструктор, що сприймає один
G[S, Int] // недійсне: S cобмежує свій параметр відповідним до String,
// G очікує конструктор типу з параметром, що відповідає Int
SimpleType ::= ‘(’ Types ‘)’
Кортежний
тип scala.Tuple,
де
Кортежні
класи є кейс-класи, чиї поля можуть бути доступні через селектори _1 , … , _n.
Їх функціональність абстрагована в відповідному трейті Product. n-арний кортежний клас та
вироблений трейт визначені щонайменьше як наступне в стандартній
бібліотеці Scala (вони можуть також додавати інші методи та реалізувати
інші трейти).
case class Tuplen [+ T1, … , + Tn](_1: T1, … , _n: Tn)
extends Product_n[ T1, … , Tn]
trait Product_n[+ T1, … , + Tn] {
override def productArity = n
def _1: T1
…
def _n: Tn
}
AnnotType ::= SimpleType {Annotation}
Анотований
тип
Наступний
тип додає анотацію @suspendable
до типу String:
String @suspendable
CompoundType ::= AnnotType {‘with’ AnnotType} [Refinement]
| Refinement
Refinement ::= [nl] ‘{’ RefineStat {semi RefineStat} ‘}’
RefineStat ::= Dcl
| ‘type’ TypeDef
|
Складний
тип with … with
В декларації методу в структурному уточненні, тип кожного значення-параметру може тільки посилатись на параметри типів або абстрактні типи, що містяться в уточненні. Тобто він має посилатись на параметр типу самого методу, або на визначення типу в уточненні. Це обмеження не стосується до результуючого типу методу.
Якщо
надане уточнення, додається явне пусте уточнення, тобто
with … with with … with
Складний
тип може також складатись тільки з уточнення AnyRef
Наступний приклад показує, як декларувати та використовувати метод, який є паметром-типом, що містить уточнення з структурованими деклараціями.
case class Bird (val name: String) extends Object {
def fly(height: Int) = …
…
}
case class Plane (val callsign: String) extends Object {
def fly(height: Int) = …
…
}
def takeoff(runway: Int, r: { val callsign: String; def fly(height: Int) }) = {
tower.print(r.callsign + " потребує злету на полосі " + runway)
tower.read (r.callsign + " готовий до злету")
r.fly(1000)
}
val bird = new Bird("Polly the parrot"){ val callsign = name }
val a380 = new Plane("TZ-987")
takeoff(42, bird)
takeoff(89, a380)
Хоча Bird та
Plane не поділяють жодного батьківського
класу, окрім Object,
параметр r методу takeoff визначений за допомогою уточнення зі
структурною декларацією сприймати любий об'єкт, що декларує
значенняcallsign та метод fly.
InfixType ::= CompoundType {id [nl] CompoundType}
Інфіксний
тип op op, що застосовується до двох операторів
типу opop може бути довільним
ідентифікатором.
Всі інфіксні оператори типів мають однаковий приоритет; дужки можуть застосовуватись для групування. Асоціативність типового оператора визначається як для термових операторів: оператори типів, що закінчуються на дві крапки ‘:’ є право-асоциативними; всі інші є ліво-асоциативними.
В
послідовності типових інфіксних операторів
Type ::= FunctionArgs ‘=>’ Type
FunctionArgs ::= InfixType
| ‘(’ [ ParamType {‘,’ ParamType } ] ‘)’
Тип
Функціональні
типи асоціюються зправа, тобто
Функціональні
типи є скороченнями для класових типів, що визначають функції apply.
Зокрема, Function. Такі класові типи визначені в бібліотеці Scala для
package scala
trait Function_n[-T1 , … , -Tn , +R] {
def apply(x1: T1 , … , x n: T n): R
override def toString = ""
}
Таким чином, функціональні типи є коваріантними в частині результуючого типу, та контрваріантними за типами аргументів.
Type ::= InfixType ExistentialClauses
ExistentialClauses ::= ‘forSome’ ‘{’ ExistentialDcl
{semi ExistentialDcl} ‘}’
ExistentialDcl ::= ‘type’ TypeDcl
| ‘val’ ValDcl
Екзистенціальний
тип має форму , де
Нехай
[
можуть бути відсутніми). Сферою кожного типу . Змінні типу, що зустрічаються в типі
примірник типу з
є типом , є об'єднанням множини значень всіх
його примірників типів.
Сколемізація
є примірником типу
Екзістенціальні типи дотримуються таким чотирьом еквівалентностям:
T
forSome { Q
} forSome { Q′
} еквівалентно
до T
forSome { Q
; Q′ }.T
forSome { Q
; Q′ }, де жодний з типів, визначених в T
forSome {Q }.T
forSome { } еквівалентно
до T
forSome { Q
}, де
type
t[tps]>:L<:U , є еквівалентним до типу typeT′
forSome { Q
}, де За
синтаксичної домовленості, твердження прив'язки в екзістенціальному типі
може також містити декларації значення val
.
Екзістенціальний тип трактується як
скорочення для типу , де на
WildcardType ::= ‘_’ TypeBounds
Scala
підтримує синтаксис заповнювача для екзестенціальних типів. Узагальнений
тип записується у формі _.
Обоє тверджень межі можуть бути відсутні. Якщо нижнє обмежувальне
твердження >: відсутнє, передбачається
>:. Якщо відсутня верхня межа <:,
передбачаєтся <:.
Узагальнені типи є скороченням до екзостенціальних квантифікованих змінних
типів, де екзистенціальна квантифікація є неявною.
Узагальнений
тип має з'являтись як аргумент типу параметризованого типу. Нехай
_.
Тоді
p.c[targs,t,targs′] forSome { type t >: L <: U }
де
Розглянемо визначення класу
class Ref[T]
abstract class Outer { type T } .
Ось декілька прикладів екзистенціальних типів:
Ref[T] forSome { type T <: java.lang.Number }
Ref[x.T] forSome { val x: Outer }
Ref[x_type # T] forSome { type x_type <: Outer with Singleton }
Останні два типи в цьому списку еквівалентні. Альтернативне формолювання першого тпу вище, з використанням узагальнуючого синтаксису:
Ref[_ <: java.lang.Number]
Тип List[List[_]] еквівалентний до екзістенціального типу
List[List[t] forSome { type t }] .
Розглянемо коваріантний тип
class List[+T]
Тип
List[T] forSome { type T <: java.lang.Number }
еквівалентний (за правилом спрощення 4 вище) до такого
List[java.lang.Number] forSome { type T <: java.lang.Number }
що,
в свою чергу, еквівалентний (за правилами спрощення 2 та 3 вище) до List[java.lang.Number].
Типи, описані в наступному, не представляють набір значень, а також не зустрічаються явно в програмах. Вони вводяться в цій доповіді як внутрішні типи визначених ідентифікаторів.
Метод
типів визначається внутрішньо як
Типи
методів асоціюються зправа:
Спеціальним
випадком є типи методів без параметрів. Вони записуються як =>
T. Методи без параметрів іменують вирази, що пере-обчислюються
кожного разу, коли посилаються на ім'я такого методу .
Типи методів не існують як типи значень. Якщо ім'я метода використовується як значення, його тип неявно конвертується до відповідного функціонального типу.
Декларації
def a: Int
def b (x: Int): Boolean
def c (x: Int) (y: String, z: String): String
продукують типізації
a: => Int
b: (Int) Boolean
c: (Int) (String, String) String
Тип
поліморфного метода позначається внутрішньо як
[ , де
[ є розділом типових параметрів [ для деякого , що задовільняють до нижніх меж та верхніх обмежень
,
дають результати типу
Декларації
def empty[A]: List[A]
def union[A <: Comparable[A]] (x: Set[A], xs: Set[A]): Set[A]
продукують типізації
empty : [A >: Nothing <: Any] List[A]
union : [A >: Nothing <: Comparable[A]] (x: Set[A], xs: Set[A]) Set[A]
Конструктор
типу представлений внутрішньо, здебільшо як метод поліморфного методу. [ представляє тип, що очікується параметром
конструктора типу або прив'язкою
абстрактного конструктора типу
з відповідним твердженням типового параметру.
Розлянемо
цей фрагмент класу Iterable[+X]:
trait Iterable[+X] {
def flatMap[newType[+X] <: Iterable[X], S](f: X => newType[S]): newType[S]
}
Концептуально,
конструктор типу Iterable є ім'ям для анонімного типу [+X]
Iterable[X], що може бути переданий до параметра конструктора
типу newType у flatMap.
Типи членів класів залежать від шляху, яким посилаються на члени. Центральними тут є три нотації, а саме:
Ці нотації визначені взаємно рекурсивно наступним чином.
Набір базових типів типу є набір класових типів, наданих таким чином.
T1
with … with Tn
{ R
}.C [T1,…,Tn ] є базові типи типу p .type є базові типи типу T1
with …
with Tn
{ R }
є скорочене
об'єднання базових
класів всіх Si #C [Ti1,…,Tin ] S #T визначаються наступним
чином. Якщо S #T є базовими типами T
forSome { Q
} є всі типи S
forSome { Q
}, де Нотація
типу .
Тоді ми визначаємо наступне.
S
= ϵ .type,
тоді S′
forSome { Q
}, та T′
forSome {Q }.D [U1,…,Un ],
для деяких параметрів типу [U1,…,Un ],
тоді D .this.type для деякого класу Якщо
Прив'язки
членів типу
Визначення проекції
типу S#T є прив'язка члена T в
S.
В цьому випадку ми також кажемо, що S#T визначений за допомогою
Ми визначаємо два відношення між типами.
| Ім'я | Символічно | Інтерпретація |
|---|---|---|
| Еквівалентність | ||
| Відповідність | Тип |
Еквівалентність
type
t
= T ,
тоді q .type,
тоді p .type
≡q .type.O .this.type
≡p .type.Відношення
відповідності
scala.Nothing
<: T
<: scala.Any.Для
кожного конструктору типу scala.Nothing
<: .
Для
кожного типу класу також
маємо scala.Null
<: .
Змінна
типу або абстрактний тип
Тип класу або параметризований тип відповідає любому з його базових типів.
Тип
синглтону відповідає до типу шляху
Тип
синглтона відповідає до типу
scala.Singleton.
Проекція
типу відповідає , якщо
Параметризований
тип відповідає
, якщо дотримуються три умови
для
Складний
тип відповідає до кожного зі
своїх компонентних типів
Якщо .
Екзістенціальний
тип відповідає
Тип , якщо .
Якщо
Поліморфний
тип
Конструктори
типів
Декларація
або визначення в деякому складному типі типу класу
type
t [T1 ,
… , Tn ]
= T відносить псевдоним типу type
t [T1 ,
… , Tn ]
= T′ , якщо typet
[T1 ,
… , Tn ]
>:
L
<: U відносить декларацію
типу type t [T1 ,
… , Tn ]
>: L′
<: U′ , якщо type
t[T1 ,
… , Tn ]
>: L <: U if Відношення
Найменша вища межа або найбільша нижня межа набору типів не завжди існують. Наприклад, розглянемо такі визначення класів.
class A[+T] {}
class B extends A[B]
class C extends A[C]
Тоді
типи A[Any],
A[A[Any]], A[A[A[Any]]], ...
формують спадаючу послідовність верхніх обмежень для B та
C.
Найменше вище обмеження може бути безкінечним для цієї послідовності, що
не існує як тип Scala. Оскільки випадки як наведений вище загалом
неможливо детектувати, компілятор Scala вільний відклонити терм, який має
тип, вказаний як найменше вище обмеження, та це обмеження буде більш
складним, ніж деякий встановлений компілятором ліміт
5.
Найменьше
вище обмеження або найбільше нижнє можуть також не бути унікальними.
Наприклад, A
with B та
B
with A обоє є більшим нижнім
обмеженням для A та
B.
Якщоre є декілька найменьших вищих обмежень, компілятор Scala може обрати
любий з них.
В
деяких ситуаціях Scala використовує більш загальне відношення
відповідності. Тип
Byte <:w Short
Short <:w Int
Char <:w Int
Int <:w Long
Long <:w Float
Float <:w Double
Слабке найменьше вище обмеження є найменьшим вищим обмеженням відповідно до слабкої відповідності.
Непостійність типу апроксимує можливість того, що параметр типу або абстрактного типу примірнику типу не мають жодних значень не-null. Значення члена або непостійний тип не можуть з'явитись в шляху.
Тип є непостійний, якщо він підпадає до однієї з чотирьох категорій:
Складний
тип є непостійний, якщо виконується
одна з двох умов.
Тут
тип
Визначник типу є непостійний, якщо він є псевдонимо непостійного типу, або він визначає параметр типу або абстрактний тип, що має непостійний тип в якості верхньої межі.
ТИп
синглтону є непостійним, якщо
підлеглий тип шляху
Екзістенціальний
тип є непостійним, якщо
Тип
називається загальним, якщо він містить аргументи типів
або змінні типу. Очищення
типу є
відображення з (можливо загальних) типів на не-загальні типи. Ми
пишемо
scala.Array[T1] є
scala.Array[|T1|] .p .type є очищення типу T #x є
|T |#x .T1
with …
with Tn
{R } є очищення домінатора перетину
T
forSome {Q } є
Домінатор перетину списку типів
Ми вважаємо, що об'єкти та пакунки також неявно визначають клас (з тим же ім'ям, що і об'єкт або пакунок, але недоступний для користувацьких програм).
Посилання на структурно визначений член (виклик метода або доступ до значення або змінної) може генерувати двоїчний код, що значно повільніший, ніж еквівалентний код для неструктурних методів.
Конгруентність є відношенням еквівалентності, що закрите під формацією контекстів.
Тип
методу я неявним, якщо розділ параметрів, що визначає його,
починається з ключового слова implicit.
Поточний компілятор Scala обмежує рівень вкладання параметрізації в таких межах, що він щонайбільше на два рівні глибший, ніж максимальний рівень вкладення типів операндів.
Dcl ::= ‘val’ ValDcl
| ‘var’ VarDcl
| ‘def’ FunDcl
| ‘type’ {nl} TypeDcl
PatVarDef ::= ‘val’ PatDef
| ‘var’ VarDef
Def ::= PatVarDef
| ‘def’ FunDef
| ‘type’ {nl} TypeDef
| TmplDef
Декларація вводить імена та надає їм типи. Воа може формувати частину визначення класу або уточнення в складному типі.
Визначення вводить імена, що позначають терми або типи. Воно може формувати частину визначення об'єкту або класу, або воно може бути локальним відносно блока. Обоє, декларації та визначення, продукують прив'язки , що асоціюють імена типів з визначеннями типів або обмеженнями, і так асоціюються імена термів з типами.
Сферою
видимості імені, що вводиться декларацією або визначенням, є вся
послідовність тверджень, що містять прив'язку. Однак є обмеження на
попередні посилання в блоках: в послідовності тверджень
Dcl ::= ‘val’ ValDcl
ValDcl ::= ids ‘:’ Type
PatVarDef ::= ‘val’ PatDef
PatDef ::= Pattern2 {‘,’ Pattern2} [‘:’ Type] ‘=’ Expr
ids ::= id {‘,’ id}
Декларація
значення val
вводить
Визначення
значення val
визначає
Обчислення
визначення значення має на увазі обчислення правої сторони lazy.
Ефектом визначенн значення є прив'язка
lazy значення обчислює праву сторону
Визначення значення константи має форму
final val x = e
де e є виразом
константи. Модифікатор final мусить бути присутнім, та
анотація типу не може бути надана. Посилання на значення константи x саме трактується як вираз
константи; в згенерованому коді вони замінюються на праву сторону e
визначення.
Визначення
значень можуть альтернативно мати шаблон в якості лівої сторони. Якщо val
розширюється до наступного:
val $x = e match {case p => ( x1,…,xn)}
val x1 = $x._1
…
val xn = $x._n .
Тут
val x = e match { case p => x }
e match { case p => ()}
Наступне є прикладами визначень типу
val pi = 3.1415
val pi: Double = 3.1415 // еквівалентно першому визначенню
val Some(x) = f() // визначення з шаблоном
val x :: xs = mylist // визначення з інфіксним шаблоном
Останні два визначення мають наступні розширення.
val x = f() match { case Some(x) => x }
val x$ = mylist match { case x :: xs => (x, xs) }
val x = x $._1
val xs = x $._2
Ім'я
кожного задекларованого або визначеного значення не може закінчуватись
на _=.
Декларація
значення val
є скороченням для послідовності
декларацій значень val
.
Визначення значення val
є скороченням для послідовності
визначень val
.
Визначення значення val
є скороченням для послідовності
визначень значень val
.
Dcl ::= ‘var’ VarDcl
PatVarDef ::= ‘var’ VarDef
VarDcl ::= ids ‘:’ Type
VarDef ::= PatDef
| ids ‘:’ Type ‘=’ ‘_’
Декларація
змінної var
є еквівалентною до декларації
обох, функції геттера(отримувача, від getter) :
def x : T
def x_= ( y: T): Unit
Реалізація класа може визначити задекларовану змінну з використанням визначення змінної, або визначенням відповідних методів сеттера та геттера.
Визначення
змінної var вводить несталу змінну з типом
Визначення
змінної альтернативно можуть мати шаблон в якості лівої частини.
Визначення змінної var
, де val
,
за винятком того, що вільні імена в
Ім'я
кожної задекларованої або визначеної змінної не може закінчуватись
на _=.
Визначення
змінної var може з'явитись тільки як
член шаблону. Воно вводить змінне поле з типом
| замовчання | тип |
|---|---|
0 |
Int або один з його
типів-піддиапазонів |
0L |
Long |
0.0f |
Float |
0.0d |
Double |
false |
Boolean |
() |
Unit |
null |
всі інші типи |
Коли
вони з'являються як члени шаблону, обоє форми визначення змінної також
вводять функцію геттера , що змінює значення, наразі присвоєне
змінній. Функції мають ти ж сигнатури, як і для декларацій
змінних. Шаблон матиме ці функції геттера та сеттера як члени, в той час
як оригінальна змінна не може бути доступна напряму як член шаблону.
Наступний
приклад показує як в Scala можуть бути симульовані властивості.
Він визначає клас TimeOfDayVar зі змінними часу, де оновлювані
цілі поля представляють години, хвилини та секунди. Їх реалізація містить
перевірки, що дозволяють присвоїти цім полям тільки легальні значення.
Користувацький код, з іншого боку, має доступ до ціх полів як до звичайних
змінних.
class TimeOfDayVar {
private var h: Int = 0
private var m: Int = 0
private var s: Int = 0
def hours = h
def hours_= (h: Int) = if (0 <= h && h < 24) this.h = h
else throw new DateError()
def minutes = m
def minutes_= (m: Int) = if (0 <= m && m < 60) this.m = m
else throw new DateError()
def seconds = s
def seconds_= (s: Int) = if (0 <= s && s < 60) this.s = s
else throw new DateError()
}
val d = new TimeOfDayVar
d.hours = 8; d.minutes = 30; d.seconds = 0
d.hours = 25 // підіймається виключення DateError
Декларація
змінної var є скороченням для послідовності
декларацій змінних var
.
Визначення змінної var
є скороченням для послідовності
визначень змінних var
.
Визначення змінної var є скороченням для послідовності
визначень змінних var.
Dcl ::= ‘type’ {nl} TypeDcl
TypeDcl ::= id [TypeParamClause] [‘>:’ Type] [‘<:’ Type]
Def ::= ‘type’ {nl} TypeDef
TypeDef ::= id [TypeParamClause] ‘=’ Type
Декларація типу type декларує [ відсутнє,
Якщо
декларація типу з'являється як член-декларація типу, реалізації типу
можуть реалізувати scala.Nothing.
Якщо відсутня вища межа scala.Any.
Декларація
конструктора типу накладає додаткові обмеження на конкретні типи, які
можуть стояти за
Сфера
видимості параметру типу розширюється за межі
>:
, та самий вираз параметру типу
Щоб
продемонструвати вкладені сфери, всі наступні декларації еквівалентні: type
t[m[x] <: Bound[x], Bound[x]],
type t[m[x] <: Bound[x], Bound[y]]
та type
t[m[x] <: Bound[x], Bound[_]], позаяк сфера дії, тобто типу
параметру t[MutableList,
Iterable] є дійсним
використанням
Псевдоним типу type визначає type .
Сфера параметру типу розширюється над правою частиною
Правила
сфери видимості для визначень and
параметрів типу роблять можливим, щоб ім'я типу
з'являлось у власному обмеженні в його правій частині. Однак буде
статичною помилкою, якщо псевдоним типу посилається рекурсивно на сам
визначений конструктор типу. Тобто, тип type не може посилатись прямо або
непрямо на ім'я
Далі слідують легальні декларації та визначення типів:
type IntList = List[Integer]
type T <: Comparable[T]
type Two[A] = Tuple2[A, A]
type MyCollection[+X] <: Iterable[X]
Наступне нелегальне:
type Abs = Comparable[Abs] // рекурсивний псевдонім типу
type S <: T // S, T обмежені самі собою
type T <: S
type T >: Comparable[T.That] // Неможливо вживати селектор для T,
// T є типом, а не значенням
type MyCollection <: Iterable // Члени конструктора типу мусять явно
// вказати свої параметри типу.
Якщо
псевдонім типу type посилається на клас типу
Об'єкт Predef містить визначення, що
встановлює Pair як псевдонім для параметризованого
класу Tuple2:
type Pair[+A, +B] = Tuple2[A, B]
object Pair {
def apply[A, B](x: A, y: B) = Tuple2(x, y)
def unapply[A, B](x: Tuple2[A, B]): Option[Tuple2[A, B]] = Some(x)
}
Як
слідоцтво, для кожних двох типів Pair[ є еквівалентним до типу Tuple2[. Pair також може бути використаний як
конструктор замість Tuple2,
як тут, :
val x: Pair[Int, String] = new Pair(1, "abc")
TypeParamClause ::= ‘[’ VariantTypeParam {‘,’ VariantTypeParam} ‘]’
VariantTypeParam ::= {Annotation} [‘+’ | ‘-’] TypeParam
TypeParam ::= (id | ‘_’) [TypeParamClause] [‘>:’ Type] [‘<:’ Type] [‘:’ Type]
Параметри
типів з'являються в визначеннях типів, визначеннях класів, та визначеннях
функцій. В цьому розділі ми розглянемо тільки визначення параметрів типу з
нижнім обмеженням >: та верхнім обмеженням <: , в той час як дискусія щодо
контекстних обмежень : та обмежень перегляду <% відкладені до наступного разу.
Найбільш
загальною формою параметрів типу першого порядку є
.
Тут +,
або -.
Одна або більше анотацій можуть передувати параметру типу.
Імена всіх параметрів типу мусять бути попарно різними в замикаючому їх твердженні параметрів типу. Сфера дії параметру типу включає в жодному разі ціле твоердження параметру типу. Таким чином, можливо, щоб параметр типу з'являвся як частина власного обмеження, або обмеження для інших параметрів типу, в тому ж твердженні. Однак параметр типу не може бути обмежений прямо або непрямо самим собою.
Параметр
конструктора типу додає вкладене твердження параметру типу до параметру
типу. В найбільш загальній формі параметр конструктора типу є
.
Надані
вище обмеження дії узагальнені для випадка вкладених визначень параметрі
типу. Параметри типу вищого порядку (параметри типу параметрів типу ‘_’,
що ніде не видиме.
Ось деяки гарно сформовані приклади параметрів типу:
[S, T]
[@specialized T, U]
[Ex <: Throwable]
[A <: Comparable[B], B <: A]
[A, B >: A, C >: A <: B]
[M[X], N[X]]
[M[_], N[_]] // еквівалентно попередньому
[M[X <: Bound[X]], Bound[_]]
[M[+X] <: Iterable[X]]
Наступні приклади параметрів типу є нелегальними:
[A >: A] // нелегально, `A' обмежує саме себе
[A <: B, B <: C, C <: A] // нелегально, `A' має себе за обмеження
[A, B, C >: A <: B] // нелегально, нижне обмеження `A' для `C' не
// відповідає верхньому обмеженню `B'.
Анотації варіантності індикують, як примірники параметризованих типів варіюють відповідно до суб-типізації. Варіантність ‘+’ вказує на коваріантну залежність, варіантність ‘-’ вказує на контрваріантну залежність, та відсутність варіантності вказує на інваріантну залежність.
Анотація
варіантності обмежує шлях, яким анотована змінна циклу може з'являтись в
типі або класі, що прив'язує параметр типу. В визначенні типу type ,
або в декларації type
параметри типу, помічені ‘+’,
повинні з'являтись тільки в коваріантній позиції, в той час як параметр
типу, що помічений ‘-’, має з'являтись тільки в контрваріантній позиції.
Аналогічно, для визначення класу class
, параметр типу, позначений ‘+’, має з'являтись тільки в
коваріантній позиції в самому типі
Позиція варіації параметру типу в типі або в шаблоні визначена наступним чином. Давайте протиставимо коваріантність та контрваріантність, та все це протиставимо інваріантності. Вищий рівіень типу або шаблону завжди в коваріантній позиції. Позиція варіантності змінюється в наступних конструкціях.
S #T завжди в інваріантній позиції.S […T… ]:
Якщо відповідний параметр типу є інваріантним, тоді S […T… ].Посилання на параметри типу в об'єкт-приватних, або об'єкт-захищених значеннях, типах, змінних, або методах класу не перевіряються щодо їх варіантної позиції. У ціх членах параметр типу може з'являтись будь-де без обмеження його легальної анторації варіантності.
Наступна анотація варіантності є легальною.
abstract class P[+A, +B] {
def fst: A; def snd: B
}
З
ціма анотаціями варіантності примірники типу підтипу
P[IOException, String] <: P[Throwable, AnyRef]
Якщо
члени
abstract class Q[+A, +B](x: A, y: B) {
var fst: A = x // **** помилка: нелегальная варіантність:
var snd: B = y // `A', `B' з'являється в інваріантній позиції.
}
Якщо несталі змінні є об'єкт-приватними, визначення класу знову стає легальним:
abstract class R[+A, +B](x: A, y: B) {
private[this] var fst: A = x // OK
private[this] var snd: B = y // OK
}
Наступна
анотація варіантності є нелегальною, оскільки append:
abstract class Sequence[+A] {
def append(x: Sequence[A]): Sequence[A]
// **** помилка: нелегальна варіантність:
// `A' з'являється в контрваріантній позиції.
}
Проблеми
можна уникнути, генералізуючи тип append за допомогою нижнього обмеження:
abstract class Sequence[+A] {
def append[B >: A](x: Sequence[B]): Sequence[B]
}
abstract class OutputChannel[-A] {
def write(x: A): Unit
}
З
цією анотацією ми маємо, що OutputChannel[AnyRef] відповідає OutputChannel[String].
Таким чином, канал, по якому можна писати будь-який об'єкт, може
замінювати канал, по якому ви можете писати тільки рядки.
Dcl ::= ‘def’ FunDcl
FunDcl ::= FunSig ‘:’ Type
Def ::= ‘def’ FunDef
FunDef ::= FunSig [‘:’ Type] ‘=’ Expr
FunSig ::= id [FunTypeParamClause] ParamClauses
FunTypeParamClause ::= ‘[’ TypeParam {‘,’ TypeParam} ‘]’
ParamClauses ::= {ParamClause} [[nl] ‘(’ ‘implicit’ Params ‘)’]
ParamClause ::= [nl] ‘(’ [Params] ‘)’}
Params ::= Param {‘,’ Param}
Param ::= {Annotation} id [‘:’ ParamType] [‘=’ Expr]
ParamType ::= Type
| ‘=>’ Type
| Type ‘*’
Декларація
функції має форму def
,
де def також включає тіло
функції, [,
за яким слідує нуль або більше значень параметрів
(.
Така декларація або визначення вводить значення з (можливо поліморфним)
типом методу, чиї параметри типу та результата надані.
Очікується, що тип тіла функції відповідає до задекларованого типу результату, якщо такий надано. Якщо декларація типу не рекурсивна, тип результата можна опустити, в якому разі він визначається з типу тіла функції.
Параметр
типу
Параметр
значення або
,
яке пирв'язує значення параметрів з їх типами.
Кожна
декларація значення параметру може додатково визначати аргумент по
замовчанню. Вираз аргумента по замовчанню
Для
кожного параметра , що обчислює вираз аргумента по
замовчанню. Тут [, та всі твердження параметрів значень ( передують
недоступні для користувацьких
програм.
В методі
def compare[T](a: T = 0)(b: T = a) = (a == b)
вираз
по замовчанню 0 типізований з невизначеним
очікуваним типом. Коли застосовується compare(),
вставляється значення по замовчанню 0
та T інстанціюється як Int.
Методи, що обчислюють аргументи по замовчанню мають форму:
def compare$ default $1[T]: Int = 0
def compare $default $2[T](a: T): T = a
Сфера
дії формального параметру значення
Значення по замовчанню, що залежить від ранішніх параметрів, використовує справжні аргументи, якщо вони надані, не аршументи по замовчанню.
def f(a: Int = 0)(b: Int = a + 1) = b // OK
// def f(a: Int = 0, b: Int = a + 1) // "помилка: не знайдено: значення a"
f(10)() // повертає 11 (не 1)
ParamType ::= ‘=>’ Type
Тип
значення параметру може може мати префікс =>,
тобто .
Тип такого параметра потім є непараметризованим типом метода => .
Це вказує на те, що відповідний аргумент не обчислюється в точці
застосування функції, але замість цього обчислюється при кожному
використанні в функції. Тобто аргумент обчислюється з використанням виклик-за-ім'ям.
Модифікатор
за-ім'ям недоступний для параметрів класів, що мають префікс
val або
var,
включаючи параметри кейс класів, для яких префікс val генерується неявно. Модифікатор
за-ім'ям також не допускається для неявних
параметрів.
Декларація
def whileLoop (cond: => Boolean) (stat: => Unit): Unit
вказує,
що обоє параметри whileLoop обчислюються з використанням
виклику-за-ім'ям.
ParamType ::= Type ‘*’
Останнє
значення параметру розділу параметрів може мати суфікс '*',
тобто (..., .
Тип такого повторюваного параметру зсередини методу буде
послідовністю типу scala.Seq[.
Методи з повторюваними параметрами
приймають змінне число аргументів типу ( застосований до аргументів _*.
Якщо (,
тоді тип (.
Не допускається визначати жодні аргументи по замовчанню в розділі параметрів з повторюваним параметром.
Наступне визначення метода обчислює суму квадратів змінного числа цілих аргументів.
def sum(args: Int*) = {
var result = 0
for (arg <- args) result += arg
result
}
Наступні
застосування цього метода дає 0, 1, 6,
в такому порядку.
sum()
sum(1)
sum(1, 2, 3)
Більше того, розглянемо визначення:
val xs = List(1, 2, 3)
Наступне
застосування метода sum є хворобливо-сформованим:
sum(xs) // ***** помилка: очікується: Int, знайдено: List[Int]
Для
контрасту, наступне застосування добре сформоване, та знову дає
результат 6:
sum(xs: _*)
FunDcl ::= FunSig
FunDef ::= FunSig [nl] ‘{’ Block ‘}’
Для
процедур існує спеціальний синтаксис, тобто функцій, що повертають
значення Unit ().
Декларація процедури є декларацією функції, де тип результата пропущений.
Тип результату потім неявно завершується до типу Unit.
Тобто, def еквівалентно до def .
Визначення
процедури є визначення функції, де тип результату та знак рівності
відсутні; її визначений вираз має бути блоком. Тобто, def is еквівалентне до
def .
Ось
декларація та визначення процедури з ім'ям write:
trait Writer {
def write(str: String)
}
object Terminal extends Writer {
def write(str: String) { System.out.println(str) }
}
Код вище неявно завершений до наступного коду:
trait Writer {
def write(str: String): Unit
}
object Terminal extends Writer {
def write(str: String): Unit = { System.out.println(str) }
}
Визначення
члену класу
Розглянемо наступне визначення:
trait I {
def factorial(x: Int): Int
}
class C extends I {
def factorial(x: Int) = if (x == 0) 1 else x * factorial(x - 1)
}
Тут
є прийнятним не вказувати тип реузльтату factorial в
C,
навіть при тому, що він рекурсивний .
Import ::= ‘import’ ImportExpr {‘,’ ImportExpr}
ImportExpr ::= StableId ‘.’ (id | ‘_’ | ImportSelectors)
ImportSelectors ::= ‘{’ {ImportSelector ‘,’}
(ImportSelector | ‘_’) ‘}’
ImportSelector ::= id [‘=>’ id | ‘=>’ ‘_’]
Твердження
імпорту мають форму import , де
{ x1 => y1,…,xn => yn, _ }
для ‘_’ може бути відсутнім. Це робить
доступним кожний імпортабельний член
під некваліфікованим іменем перейменовує на
також будуть зроблені доступними
під їх власними некваліфікованими іменами.
Селектори
імпорту роблять таким же чином для типів та членів термів. Наприклад,
твердження імпорту import перейменовує терм з імені
на терм імені
на тип імені
Якщо
ціль селектору імпорту є підстановочний символ, селектор імпорту приховує
доступ до первинного члена. Наприклад, селектор імпорту “перейменовує”
Поле зору прив'язки, що воодиться твердженням імпорту починається беспосередньо після цього твердження імпорту, та продовжуєтьсядо кінця охоплюючого блоку, шаблону, твердження пакунку, або одиниці компіляції, що настане першим.
Існують
декільки скорочень. Селектор імпорту може бути тільки простим ім'ям
.
Більше того, можливо замінити цілий список селекторів імпорту на один
ідентифікатор або символ підстановки. Твердження імпорту import is еквівантне до import ,
тобто воно робить доступним без кваліфікації член
import є еквівалентним до
import ,
тобто воно робить доступним без кваліфікації всі члени з import в Java).
Твердження
імпорту з декількома виразами імпорту import інтерпретується як послідовність
тверджень імпорту
import .
Розглянемо визначення об'єкта:
object M {
def z = 0, one = 1
def add(x: Int, y: Int): Int = x + y
}
Тоді блок
{ import M.{one, z => zero, _}; add(zero, one) }
еквіваленний до блока
{ M.add(M.z, M.one) }
LocalModifier ::= ‘implicit’
ParamClauses ::= {ParamClause} [nl] ‘(’ ‘implicit’ Params ‘)’
Члени
шаблону та параметри, відмічені модифікатором implicit, можуть бути передані як неявні
параметри, та можуть використовуватись як неявні перетворення, що
називаються переглядами.
Модифікатор implicit є недійсним для всіх типованих
членів, так само, як для об'єктів вищого
рівня.
Наступний
код визначає абстрактний клас моноїдів, та дві конкретні реалізації, StringMonoid та
IntMonoid.
Дві реалізації позначені як implicit.
abstract class Monoid[A] extends SemiGroup[A] {
def unit: A
def add(x: A, y: A): A
}
object Monoids {
implicit object stringMonoid extends Monoid[String] {
def add(x: String, y: String): String = x.concat(y)
def unit: String = ""
}
implicit object intMonoid extends Monoid[Int] {
def add(x: Int, y: Int): Int = x + y
def unit: Int = 0
}
}
Список
неявних параметрів (implicit маркує параметри
Метод
з неявними параметрами може бути застосований до аргументів, так само, як
звичайний метод. В цьому випадку мітка implicit не має ефекта. Однак, якщо методу бракує
аргументів для його неявних параметрів, такі аргументи будуть
запроваджені автоматично.
Справжні
аргументи, що придатні для передачі до неявного параметра типу
implicit члени деякого об'єкту, що належить до
неявної сфери неявного типу параметру,
Неявна сфера типу
Частини типу
T1
with …
with Tn ,
об'єднання частин S [T1,…,Tn ],
о'бєднання частин p .type,
частини типу S #U ,
яастини T forSome { ... } є частинами
T);Зауважте, що пакунки внутрішньо представлені як класи з компанійськіим модулями, що містять члени пакунку. Таким чином, неявні параметри, визначені в об'єкті пакунку, є частиною неявної сфери типу з префіксом в вигляді цього пакунку.
Якщо існує декілька придатних аргументів, що співпадають з типами неявніх параметрів, буде обраний найбільш специфічний, з використанням правил статичного розрішення перевантаження. Якщо параметр має аргумент по замовчанню, та не може бути знайдено неявний аргумент, буде використаний аргумент по замовчанню.
Беручи
класи з прикладу Monoid,
ось метод, що обчислює суму списку елементів з використанням операцій
моноїда add та
unit.
def sum[A](xs: List[A])(implicit m: Monoid[A]): A =
if (xs.isEmpty) m.unit
else m.add(xs.head, sum(xs.tail))
Моноїд,
що розглядається, помічений як неявний параметр, і, таким чином, може бути
виведений, базуючись на типі списку. Розгляньте, наприклад, виклик sum(List(1, 2, 3)) в контексті, де видимі stringMonoid та
intMonoid.
Ми знаємо, що формальний тип параметру sum потребує бути уособлений як Int.
Єдиним припустимим об'єктом, що співпадає з типом неявного формального
параметра, Monoid[Int], є intMonoid, так що цей об'єкт буде переданий як
неявний параметр.
Ця дискусія також показує, що неявні параметри застосовуються після виведення всіх типів аргументів.
Неявні
методи можуть самі по собі мати неявні параметри. Як приклад розглянемо
наступний метод з модуля scala.List,
що вводить списки до класу scala.Ordered,
за умови, що тип елементів також конвертується до цього типу.
implicit def list2ordered[A](x: List[A])
(implicit elem2ordered: A => Ordered[A]): Ordered[List[A]] =
...
Розглянемо додавання методу
implicit def int2ordered(x: Int): Ordered[Int]
що
вводить цілі в клас Ordered.
Тепер ми можемо визначити метод sort над впорядкованими списками:
def sort[A](xs: List[A])(implicit a2ordered: A => Ordered[A]) = ...
Ми
можемо застосувати sort до списку списків цілих
yss:List[List[Int]] як наступне:
sort(yss)
Виклик вище буде завершений, передаючи два вкладені неявні аргументи:
sort(yss)(xs: List[Int] => list2ordered[Int](xs)(int2ordered)) .
Можливість
передачі неявних аргументів до неявних аргументів підіймає можливість
безкінечної рекурсії. Наприклад, дехто може спробувати визначити наступний
метод, що вводить кожний тип в клас
Ordered:
implicit def magic[A](x: A)(implicit a2ordered: A => Ordered[A]): Ordered[A] =
a2ordered(x)
Тепер,
якщо хтось спробує застосувати sort до аргументу типу
arg, що не має іншого введення в
клас Ordered,
може утворитись безкінечна рекурсія:
sort(arg)(x => magic(x)(x => magic(x)(x => ... )))
Щоб
запобігти такому безкінечному розкриттю, компілятор відслідковує стек
“відкритих неявних типів”, для яких наразі відбувається пошук неявних
аргументів. Коли б не розшукувався неявний аргумент для
Кажуть,
що тип
Набір високорівневих конструкторів
типів
ttcs(T1
with …
with Tn) Складністю
complexity(T1
with …
with Tn) Коли
вводимо sort(xs) для деякого списку xs типу
List[List[List[Int]]],
послідовність типів, для яких шукаються неявні аргументи, будуть
наступними
List[List[Int]] => Ordered[List[List[Int]]],
List[Int] => Ordered[List[Int]]
Int => Ordered[Int]
Всі
типи поділяють загальний конструктор типу scala.Function1,
але складність кожного нового типу менша, ніж складність попереднього
типу. Таким чином код перевіряє тип.
Нехай ys буде списком деякого типу, що не
може бути перетворений на Ordered.
Наприклад:
val ys = List(new IllegalArgumentException, new ClassCastException, new Error)
Уявімо,
що визначення magic, надане вище, знаходиться в полі зору.
Тоді послідовність типів, для яких будуть шукатись неявні аргументи, буде
наступна
Throwable => Ordered[Throwable],
Throwable => Ordered[Throwable],
...
Оскільки другий тип в послідовності еквівалентний першому, компілятор буде видавати помилку, повідомляючи про неявне розширення, що розходиться.
Неявні
параметри та методи можуть також визначати неявні перетворення, що
називаються переглядами. Перегляд від типу або
(=>, або метод, що зводиться до значення
цього типу.
Перегляди застосовуються в трьох ситуаціях:
T => pt .
Якщо знайдений такий перегляд, вираз v (e ).v (e ).m .v (e ).m(args) .Неявний
перегляд, якщо він буде знайдений, може сприйняти аргумент
Як і для неявних параметрів, використовується розрішення перевантаженняв разі, коли є декілька можливих кандидатів (з будь якої категорії, викликаних-по-значенню або викликаних-по-імені).
Клас scala.Ordered[A] містить метод
def <= [B >: A](that: B)(implicit b2ordered: B => Ordered[B]): Boolean .
Уявть
два списки, xs та
ys, типу
List[Int], та що методи list2ordered та
int2ordered, визначені тут
є в сфері досяжності. Тоді операція
xs <= ys
є легальною, та розширюється до:
list2ordered(xs)(int2ordered).<=
(ys)
(xs => list2ordered(xs)(int2ordered))
Перше
застосування list2ordered конвертує список xs до примірнику класу Ordered,
тоді як друге трапляння є частиною неявного параметру, переданому методу <=.
TypeParam ::= (id | ‘_’) [TypeParamClause] [‘>:’ Type] [‘<:’ Type]
{‘<%’ Type} {‘:’ Type}
Типовий
параметр .
В цьому випадку параметр типу може бути утворений до кожного типу
Параметр
типу .
В цьому випадку параметр типу може бути утрворений до любого типу
Метод або клас, що містить параметр типу з обмеженням перегляду або контексту, трактується як такий, що еквівалентний методу з неявними параметрами. Розглянемо перший випадок одного параметру, що обмежений по перегляду або контексту, як цей:
def f [ A <% T1 ... <% Tm : U1 : Un]( ps): R = ...
Тоді визначення методу вище буде розширене до такого
def f [ A]( ps)(implicit v1: A => T1, ..., vm: A => Tm,
w1: U1[ A], ..., wn: Un[ A]): R = ...
де
Якщо клас або метод має декілька обмежень перегляду або контексту для параметрів типу, кожний такий параметр розширюється в доказові параметри, щоб вони з'явились та всі отримані доказові параметри конкатенуються в один розділ неявних параметрів. Оскільки трейти не сприймають параметрів конструктора, ця трансляція не робить для них. Відповідно, параметри-типи в трейтах не можуть бути перегляд- або контекст-обмеженими. Також, метод або клас з перегляд- або контекст- обмеженнями не може визначати жодні додаткові неявні параметри.
Метод <= з приклада Ordered може бути декларований більш узгоджено
наступним чином:
def <= [B >: A <% Ordered[B]](that: B): Boolean
Маніфести
є описи типу, що можуть бути автоматично згенеровані компілятором Scala як
аргументи до неявних параметрів. Стандартна бібліотека Scala містить
ієрархію чотирьох класів маніфесту, з OptManifest зверху. Їх сигнатури слідують
схемі нижче.
trait OptManifest[+T]
object NoManifest extends OptManifest[Nothing]
trait ClassManifest[T] extends OptManifest[T]
trait Manifest[T] extends ClassManifest[T]
Якщо
неявний параметр метода або конструктора є субтипом OptManifest[T], маніфест є визначений для
Перше,
якщо вже є неявний аргумент, що співпадає з
Інакше,
нехай scala.reflect.Manifest, якщо
Manifest,
або компаньоном об'єкта scala.reflect.ClassManifest інакше. Нехай Manifest, якщо
Manifest,
або трейтом OptManifest інакше. Тоді застосовуються такі
правила.
Any, AnyVal, Object, Null,
або Nothing,
маніфест для нього генерується, обираючи відповідне значення маніфесту Manifest.T ,
що існує в модулі Manifest.Array[S ],
маніфест генерується за виклика Mobj .arrayType[S](m),
де Mobj .classType[T](m0 ,
classOf[T], ms ), де Mobj .classType[T](classOf[T],
ms ), де p .type,
маніфест генерується з викликом Mobj .singleType[T](p )T1
with ,…,
with Tn , де Manifest,
тоді маніфест генерується з викликом Manifest.intersectionType[T](ms ), де ClassManifest,
тоді маніфест генерується для домінатора
перетину типів
OptManifest,
маніфест генерується з визначника scala.reflect.NoManifest.
Якщо OptManifest,
отримаємо статичну помилку.
Pattern ::= Pattern1 { ‘|’ Pattern1 }
Pattern1 ::= varid ‘:’ TypePat
| ‘_’ ‘:’ TypePat
| Pattern2
Pattern2 ::= varid [‘@’ Pattern3]
| Pattern3
Pattern3 ::= SimplePattern
| SimplePattern {id [nl] SimplePattern}
SimplePattern ::= ‘_’
| varid
| Literal
| StableId
| StableId ‘(’ [Patterns] ‘)’
| StableId ‘(’ [Patterns ‘,’] [varid ‘@’] ‘_’ ‘*’ ‘)’
| ‘(’ [Patterns] ‘)’
| XmlPattern
Patterns ::= Pattern {‘,’ Patterns}
Шаблон будується з констант, конструкторів, змінних та перевірок типу. Порівняння з шаблоном перевіряє, чи задане значення (або послідовність значень) має профіль, заданий в шаблоні, та, якщо це так, прив'язує змінні в шаблоні до відповідних компонент значення (або послідовності значень). Те ж ім'я змінної не може прив'язуватись більше одного разу в шаблоні.
Деякі приклади шабловів є:
ex: IOException співпадає з усіма примірниками класу IOException,
прив'язуючи ex до примірника.Some(x) порівнює значення форми Some(v ),
прив'язуючи x до значення аргумента Some.(x, _) співпадає з парами значень,
прив'язуючи x д опершого компонента пари. Другий
компонент співпадає з шаблоном-підстановочним символом.x :: y :: xs співпадає зі списками довжиною x до першого елементу списку, y до другого елементу, та xs до
решти.1 | 2 | 3 співпадає з цілими між 1 та 3.Співпадіння шаблону завжди відбувається в контексті, що постачає отримуваний тип шаблону. Ми розрізняємо наступні типи шаблонів.
SimplePattern ::= `_' | varid
Щаблон
змінної _ , що розглядається так, якби це
була свіжа змінна при кожному входженні.
Pattern1 ::= varid `:' TypePat | `_' `:' TypePat
Шаблон
типу
Pattern2 ::= varid `@' Pattern3
Сполучення
шаблону складається зі змінної шаблону
SimplePattern ::= Literal
Літеральний
шаблон ==)
літералу
SimplePattern ::= StableId
Шаблон
стабільного ідентифікатора є стабільний
ідентифікатор (дивіться тут).
Щоб розрішити синтаксичне перекриття з шаблоном змінної, шаблон стабільного ідентифікатора не може бути простим ім'ям, що починається з малої літери. Однак можливо замкнути такий ідентифікатор в зворотні лапки; тоді він буде розглядатись як шаблон стабільного ідентифікатора.
Розглянемо наступне визначення функції:
def f(x: Int, y: Int) = x match {
case y => ...
}
Тут y є шаблоном змінної, що співпадає
з любим значенням. Якщо ми бажаємо перетворити шаблон на шаблон
стабільного ідентифікатора, це може бути зроблене наступним чином:
def f(x: Int, y: Int) = x match {
case `y` => ...
}
Тепер
шаблон співпадає з параметром y з охоплюючої функції
f.
Тобто співпадіння відбувається тільки якщо аргументи
x та y
функції f рівні.
SimplePattern ::= StableId `(' [Patterns] `)
Шаблон
конструктора є форма
Особливий
випадок виникає, коли типи формальних параметрів
SimplePattern ::= `(' [Patterns] `)'
Шаблон
кортежів ( є псевдонімом для конструктора
шаблона scala.Tuple,
де () є унікальним значенням типу scala.Unit.
SimplePattern ::= StableId `(' [Patterns] `)'
Шаблон
екстрактора unapply або
unapplySeq, що співпадає з шаблоном.
Метод unapply в об'єкті
unapply
є Boolean.
В цьому випадку шаблон екстрактору співпадає з усіма значеннями
x .unapply(v )
дає true.unapply
є Option[T ]
для деякого типу x .unapply(v ) дає значення в формі Some(v1 ),
та unapply
є Option[(T1,…,Tn )],
для деяких типів x .unapply(v ) дає значення в формі Some((v1,…,vn )),
та кожний шаблон Метод unapplySeq в об'єкті
Option[( (якщо
m = 0,
тип Option[Seq[S]] також придатний). Цей випадок
далі дискутується нижче.
Об'єкт Predef містить визначення об'єкта
екстрактора Pair:
object Pair {
def apply[A, B](x: A, y: B) = Tuple2(x, y)
def unapply[A, B](x: Tuple2[A, B]): Option[Tuple2[A, B]] = Some(x)
}
Це
означає, що ім'я Pair може використовуватись замість Tuple2 для інформаціїї про кортеж, так само, як
для деконструкції кортежів в шаблонах. Таким чином, можливе
наступне:
val x = (1, 2)
val y = x match {
case Pair(i, s) => Pair(s + i, i * i)
}
SimplePattern ::= StableId `(' [Patterns `,'] [varid `@'] `_' `*' `)'
Шаблон
послідовності S*.
Друге, в шаблоні екстрактора unapply,
вле він визначає метод unapplySeq
з типом результата, що задовільняє Option[(T_1, ... , T_m, Seq[S])] (якщо
m = 0,
тип Option[Seq[S]] також прийнятний). Очікуваний тип
для шаблонів
Останній
шаблон в послідовності шаблонів може бути підстановочним
символом послідовності _*.
Кожний елемент шаблону
Pattern3 ::= SimplePattern {id [nl] SimplePattern}
Шаблон
інфіксної операції
Шаблон
інфіксної операції
Pattern ::= Pattern1 { `|' Pattern1 }
Альтернатива
шаблонів складається з числа
альтернативних шаблонів
XML шаблони розглядаються тут.
Шаблони регулярних виразів були припинені в Scala починаючи з версії 2.0.
Пізніші
версії Scala провадять значно спрощену версію шаблонів регулярних виразів,
що покривають більшість сценаріїїв для обробки не-текстових
послідовностей. Шаблон
послідовності є
шаблоном, що займає це місце, коли або (1) очікується шаблон типу T, який відповідає до Seq[A] для деякого
A,
або (2) конструктор кейс класу, що має ітерований формальний
параметр A*.
Шаблон підстановочної зірочки _* в самій правій позиції стоїть
замість послідовностей довільної довжини. Він може бути пров'язаний до
змінних з використанням @,
як звичайно, і в такому випадку змінна буде мати тип Seq[A].
Шаблон
TypePat ::= Type
Шаблони
типу складаються з типів, змінних типів, та підстановочних символів.
Шаблон типу
T #C .
This type pattern matches any non-null instance of the given class. Note
that the prefix of the class, if it exists, is relevant for determining
class instances. For instance, the pattern The
bottom types scala.Nothing and scala.Null cannot be used as type
patterns, because they would match nothing in any case.
p .type.
This type pattern matches only the value denoted by the path eq in class AnyRef).A
compound type pattern where each
A
parameterized type pattern _.
This type pattern matches all values which match
A
parameterized type pattern scala.Array,
where scala.Array,
where
Types which are not of one of the forms described above are also accepted as type patterns. However, such type patterns will be translated to their erasure. The Scala compiler will issue an "unchecked" warning for these patterns to flag the possible loss of type-safety.
A type variable pattern is a simple identifier which starts with a lower case letter.
Type parameter inference is the process of finding bounds for the bound type variables in a typed pattern or constructor pattern. Inference takes into account the expected type of the pattern.
Assume
a typed pattern
Type
parameter inference constructs first a set of subtype constraints over the
type variables
where
The
set
If
there exists a substitution
Otherwise,
if
The final step consists in choosing type bounds for the type variables which imply the established constraint system. The process is different for the two cases above.
We
take
We
take
In both cases, local type inference is permitted to limit the complexity of inferred bounds. Minimality and maximality of types have to be understood relative to the set of types of acceptable complexity.
Assume
a constructor pattern (_: .
Consider the program fragment:
val x: Any
x match {
case y: List[a] => ...
}
Here,
the type pattern List[a] is matched against the
expected type Any.
The pattern binds the type variable a.
Since List[a]conforms
to Any for every type argument,
there are no constraints on a.
Hence, a is introduced as an abstract
type with no bounds. The scope of a is right-hand side of its
case clause.
On
the other hand, if x is declared as
val x: List[List[String]],
this
generates the constraint List[a] <: List[List[String]],
which simplifies to a <: List[String],
because List is covariant. Hence, a is introduced with upper
bound List[String].
Consider the program fragment:
val x: Any
x match {
case y: List[String] => ...
}
Scala
does not maintain information about type arguments at run-time, so there
is no way to check that x is a list of strings.
Instead, the Scala compiler will erase the pattern to List[_];
that is, it will only test whether the top-level runtime-class of the
valuex conforms to List,
and the pattern match will succeed if it does. This might lead to a class
cast exception later on, in the case where the list x contains elements other than
strings. The Scala compiler will flag this potential loss of type-safety
with an "unchecked" warning message.
Consider the program fragment
class Term[A]
class Number(val n: Int) extends Term[Int]
def f[B](t: Term[B]): B = t match {
case y: Number => y.n
}
The
expected type of the pattern y: Number is Term[B].
The type Number does not conform to Term[B];
hence Case 2 of the rules above applies. This means that B is treated as another type
variable for which subtype constraints are inferred. In our case the
applicable constraint is Number <: Term[B],
which entails B = Int.
Hence, B is treated in the case
clause as an abstract type with lower and upper bound Int.
Therefore, the right hand side of the case clause, y.n,
of type Int,
is found to conform to the function's declared result type, Number.
Expr ::= PostfixExpr `match' `{' CaseClauses `}'
CaseClauses ::= CaseClause {CaseClause}
CaseClause ::= `case' Pattern [Guard] `=>' Block
A pattern matching expression
e match { case p1 => b1 … case pn => bn }
consists
of a selector expression if where
Let
If
no such bounds can be found, a compile time error results. If such bounds
are found, the pattern matching clause starting with
The
expected type of every block
When
applying a pattern matching expression to a selector value, patterns are
tried in sequence until one is found which matches the selector value.
Say this case is case .
The result of the whole expression is the result of evaluating scala.MatchErrorexception
is thrown.
The
pattern in a case may also be followed by a guard suffix if e with a boolean expression true,
the pattern match succeeds as normal. If the guard expression evaluates to false,
the pattern in the case is considered not to match and the search for a
matching pattern continues.
In the interest of efficiency the evaluation of a pattern matching expression may try patterns in some other order than textual sequence. This might affect evaluation through side effects in guards. However, it is guaranteed that a guard expression is evaluated only if the pattern it guards matches.
If
the selector of a pattern match is an instance of a sealed class, the compilation
of pattern matching can emit warnings which diagnose that a given set of
patterns is not exhaustive, i.e. that there is a possibility of a MatchError being raised at run-time.
Consider the following definitions of arithmetic terms:
abstract class Term[T]
case class Lit(x: Int) extends Term[Int]
case class Succ(t: Term[Int]) extends Term[Int]
case class IsZero(t: Term[Int]) extends Term[Boolean]
case class If[T](c: Term[Boolean],
t1: Term[T],
t2: Term[T]) extends Term[T]
There
are terms to represent numeric literals, incrementation, a zero test, and
a conditional. Every term carries as a type parameter the type of the
expression it represents (either Int or Boolean).
A type-safe evaluator for such terms can be written as follows.
def eval[T](t: Term[T]): T = t match {
case Lit(n) => n
case Succ(u) => eval(u) + 1
case IsZero(u) => eval(u) == 0
case If(c, u1, u2) => eval(if (eval(c)) u1 else u2)
}
Note that the evaluator makes crucial use of the fact that type parameters of enclosing methods can acquire new bounds through pattern matching.
For
instance, the type of the pattern in the second case, Succ(u),
is Int.
It conforms to the selector type T only if we assume an upper
and lower bound of Int for T.
Under the assumption Int <: T <: Int we can also verify that the
type right hand side of the second case, Int conforms to its expected
type, T.
BlockExpr ::= `{' CaseClauses `}'
An anonymous function can be defined by a sequence of cases
{ case p1 => b1 … case pn => bn }
which
appear as an expression without a prior match.
The expected type of such an expression must in part be defined. It must
be either scala.Function for some scala.PartialFunction[,
where the argument type(s)
If
the expected type is scala.Function,
the expression is taken to be equivalent to the anonymous function:
(x1:S1,…,xk:Sk ) => ( x1,…,xk) match {
case p1 => b1 … case pn => bn
}
Here,
each
new scala.Functionk [ S1,…,Sk, T] {
def apply( x1:S1,…,xk:Sk): T = ( x1,…,xk) match {
case p1 => b1 … case pn => bn
}
}
If
the expected type is scala.PartialFunction[,
the expression is taken to be equivalent to the following instance
creation expression:
new scala.PartialFunction[S , T] {
def apply( x: S): T = x match {
case p1 => b1 … case pn => bn
}
def isDefinedAt( x: S): Boolean = {
case p1 => true … case pn => true
case _ => false
}
}
Тут
isDefinedAt опущений, якщо один з шаблонів
Ось
метод, що використовує операцію лівої згортки
/: для обчислення скалярного добутку двох
векторів:
def scalarProduct(xs: Array[Double], ys: Array[Double]) =
(0.0 /: (xs zip ys)) {
case (a, (b, c)) => a + b * c
}
Кейс твердження в цьому коді еквіваленті до наступної анонімної функції:
(x, y) => (x, y) match {
case (a, (b, c)) => a + b * c
}